using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace cs2335.lab5
{
    /// <summary>
    /// This class represents an entry in the listbox.
    /// </summary>
    /// <author>Donnie Prather gtg571m</author>
    public class ListBoxEntry : IComponent
    {
        public const int ListBoxEntryHeight = 16;
        protected Rectangle listboxEntry;
        protected StringFormat entryFormat = new StringFormat(StringFormatFlags.NoClip);
        protected SolidBrush entryBackground = new SolidBrush(Color.White);
        protected Font font = new Font("SansSerif", 10);
        protected CCTForm owner;
        protected string text;
        protected bool hasFocus = false;
        protected bool hidden = false;
        protected string name;

        // the index for this entry in the listbox
        protected int entryNumber;
        // the main listbox that this entry belongs to
        protected ListBox listbox;
        // specifies whether or not this entry is currently selected
        protected bool selected = false;


        #region Properties
        /// <summary>
        /// Tells whether or not a component is hidden. If it is hidden,
        /// it should not be receiving actions.
        /// </summary>
        public bool Hidden
        {
            get { return hidden; }
        }

        public bool Selected
        {
            get { return selected; }
            set { selected = value; }
        }

        /// <summary>
        /// the index for this entry in the list of entries
        /// </summary>
        public int EntryNumber
        {
            get { return entryNumber; }
            set { entryNumber = value; }
        }

        /// <summary>
        /// Specifies the location for this listbox entry.
        /// </summary>
        public Point Location
        {
            get { return listboxEntry.Location; }
            set { listboxEntry.Location = value; Invalidate(); }
        }

        /// <summary>
        /// This is a way to set the location of this entry
        /// without redrawing everything to the screen.
        /// 
        /// This will be used for efficiency in the scrollbar
        /// implementation.
        /// </summary>
        public Point LocationNoInvalidate
        {
            set { listboxEntry.Location = value; }
        }

        public Rectangle Bounds
        {
            get { return listboxEntry; }
            set { listboxEntry = value; Invalidate(); }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public System.Drawing.Font Font
        {
            get { return font; }
            set { font = value; Invalidate(); }
        }

        public string Text
        {
            get { return text; }
            set { text = value; Invalidate(); }
        }

        public CCTForm Owner
        {
            get { return owner; }
            set { owner = value; }
        }

        public Color BackColor
        {
            get { return entryBackground.Color; }
            set { entryBackground.Color = value; Invalidate(); }
        }
        #endregion

        #region Events
        public event EventHandler Click;
        public event EventHandler GotFocus;
        public event EventHandler KeyPress;
        public event EventHandler KeyDown;
        public event EventHandler LostFocus;
        public event EventHandler Paint;
        #endregion

        /// <summary>
        /// Creates a new listbox entry.
        /// </summary>
        /// <param name="entryNumber">the index for this entry in the listbox</param>
        /// <param name="entryText">the text to display in the listbox for this entry</param>
        /// <param name="listbox">pointer to the main listbox</param>
        public ListBoxEntry(int entryNumber, string entryText, ListBox listbox)
        {
            listboxEntry = new Rectangle(0, 0, listbox.Size.Width - 1, ListBoxEntryHeight);
            this.listbox = listbox;
            entryFormat.LineAlignment = StringAlignment.Near;
            entryFormat.Alignment = StringAlignment.Near;
            text = entryText;
            this.entryNumber = entryNumber;
            /// set the location for this entry according to its entryNumber
            Location = new Point(listbox.Location.X, (listbox.Location.Y + ListBoxEntryHeight * entryNumber));
        }

        public void Invalidate()
        {
            if (owner != null)
            {
                owner.Invalidate();
            }
        }

        public void Hide()
        {
            hidden = true;
            Invalidate();
        }

        public void HideNoInvalidate()
        {
            hidden = true;
        }

        public void Show()
        {
            hidden = false;
            Invalidate();
        }

        public void ShowNoInvalidate()
        {
            hidden = false;
        }

        public void OnPaint(PaintEventArgs e)
        {
            if (!hidden)
            {
                Graphics g = e.Graphics;
                /// draw the listbox entry to the screen
                g.FillRectangle(entryBackground, listboxEntry);
                if (selected)
                {
                    /// if this entry is selected, paint its background blue and its text white
                    /// to show that it is highlighted
                    g.DrawString(text, font, Brushes.White, (RectangleF)listboxEntry, entryFormat);
                }
                else
                {
                    /// otherwise use a standard white background and black text
                    g.DrawString(text, font, Brushes.Black, (RectangleF)listboxEntry, entryFormat);
                }

                if (Paint != null)
                {
                    Paint(this, e);
                }
            }
        }

        public void OnGotFocus(EventArgs e)
        {
            hasFocus = true;

            if (GotFocus != null)
            {
                GotFocus(this, e);
            }
        }

        public void OnLostFocus(EventArgs e)
        {
            hasFocus = false;

            if (LostFocus != null)
            {
                LostFocus(this, e);
            }
        }


        public void OnMouseClick(System.Windows.Forms.MouseEventArgs e)
        {
            /// select this entry
            if (text != "")
            {
                listbox.SetSelected(entryNumber, true);
            }

            if (Click != null)
            {
                Click(this, e);
            }
        }

        public void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
        {
            /* ignore */
        }

        public void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
        {
            /* ignore */
        }

        public void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
        {
            /* ignore */
        }

        public void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
        {
            if (KeyPress != null)
            {
                KeyPress(this, e);
            }
        }

        public void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            string pressedKey = e.KeyData.ToString();

            /// if the user pressed the down arrow while this entry was selected,
            /// select the next entry below this in the list
            if (pressedKey == "Down" || pressedKey == "Right")
            {
                OnDownKey();
            }
            else if (pressedKey == "Up" || pressedKey == "Left")
            {
                OnUpKey();
            }

            if (KeyDown != null)
            {
                KeyDown(this, e);
            }
        }

        /// <summary>
        /// Called by OnKeyDown when the down arrow on the keyboard is pressed.
        /// </summary>
        public void OnDownKey()
        {
            try
            {
                ListBoxEntry nextEntry = listbox.Items[entryNumber + 1];

                if (nextEntry.Text == "")
                {
                    return;
                }

                /// if we're going down to another entry that is already visible
                if (entryNumber + 1 - listbox.TopIndex < listbox.MaxNumOfVisibleEntries)
                {
                    /// just select the next entry
                    nextEntry.OnMouseClick(null);
                    owner.FocusHolder = nextEntry;
                    nextEntry.OnGotFocus(null);
                }
                else /// otherwise, hide the top entry, move all of the entries up,
                     /// and make the new bottom entry visible
                {
                    /// hide the top entry
                    listbox.Items[listbox.TopIndex].Hide();
                    listbox.TopIndex++;

                    /// move all of the entries up one
                    foreach (ListBoxEntry entry in listbox.Items)
                    {
                        entry.Location = new Point(listbox.Location.X, (entry.Location.Y - ListBoxEntryHeight));
                    }

                    /// make the new bottom entry visible and select it
                    nextEntry.Show();
                    nextEntry.OnMouseClick(null);
                    owner.FocusHolder = nextEntry;
                    nextEntry.OnGotFocus(null);
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                // just ignore the down key if the user
                // is already at the end of the list
            }
        }

        /// <summary>
        /// Called by OnKeyDown when the up arrow on the keyboard is pressed
        /// while a listbox entry is highlighted.
        /// </summary>
        public void OnUpKey()
        {
            try
            {
                ListBoxEntry nextEntry = listbox.Items[entryNumber - 1];

                if (nextEntry.Text == "")
                {
                    return;
                }

                /// the user pressed the up arrow and we're not at
                /// the top of the list, just select the next entry
                if (entryNumber - 1 >= listbox.TopIndex)
                {
                    nextEntry.OnMouseClick(null);
                    owner.FocusHolder = nextEntry;
                    nextEntry.OnGotFocus(null);
                }
                else /// otherwise we're at the top of the list and must
                     /// move all of the entries down one, unhide the new top
                     /// entry, and hide the old bottom entry
                {
                    listbox.TopIndex--;
                    /// hide the old bottom entry
                    listbox.Items[listbox.TopIndex + listbox.MaxNumOfVisibleEntries].Hide();

                    /// move all of the entries down one
                    foreach (ListBoxEntry entry in listbox.Items)
                    {
                        entry.Location = new Point(listbox.Location.X, (entry.Location.Y + ListBoxEntryHeight));
                    }

                    /// unhide and select the new top entry
                    nextEntry.Show();
                    nextEntry.OnMouseClick(null);
                    owner.FocusHolder = nextEntry;
                    nextEntry.OnGotFocus(null);
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                // just ignore the up key if the user
                // is already at the end of the list
            }
        }
    }
}
